/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package prim;

import Interface.InterfaceBuilder;
import edu.uci.ics.jung.algorithms.layout.AbstractLayout;
import edu.uci.ics.jung.algorithms.layout.FRLayout;
import edu.uci.ics.jung.algorithms.layout.Layout;
import edu.uci.ics.jung.algorithms.layout.StaticLayout;
import edu.uci.ics.jung.algorithms.layout.util.Relaxer;
import edu.uci.ics.jung.algorithms.layout.util.VisRunner;
import edu.uci.ics.jung.algorithms.util.IterativeContext;
import edu.uci.ics.jung.graph.Forest;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.ObservableGraph;
import edu.uci.ics.jung.graph.event.GraphEvent;
import edu.uci.ics.jung.graph.event.GraphEventListener;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import edu.uci.ics.jung.visualization.control.DefaultModalGraphMouse;
import edu.uci.ics.jung.visualization.control.ModalGraphMouse;
import edu.uci.ics.jung.visualization.decorators.ToStringLabeller;
import edu.uci.ics.jung.visualization.layout.LayoutTransition;
import edu.uci.ics.jung.visualization.renderers.Renderer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.TimerTask;
import javax.swing.JRootPane;
import java.util.Timer;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Sqo
 */
public class Animator {

    private static volatile Animator m_instance;
    private int m_delay;
    private int m_verticesLeft;
    private boolean m_done;
    private IterationType m_iterationType;
    private Timer m_timer;
    private List<Edge> m_edges;
    private List<Edge> m_selectedEdges;
    private int m_previousVertex = -1;
    private Graph<String, Integer> m_graph = null;
    private Forest<String, Integer> m_tree = null;
    private VisualizationViewer<String, Integer> m_viewer = null;
    private AbstractLayout<String, Integer> m_layout = null;

    public static Animator getInstance() {
        Animator localInstance = m_instance;
        if (null == localInstance) {
            synchronized (GraphBuilder.class) {
                localInstance = m_instance;
                if (localInstance == null) {
                    m_instance = localInstance = new Animator();
                }
            }
        }
        return localInstance;
    }

    private class Edge {

        private int m_head;
        private int m_tail;

        public Edge(int head, int tail) {
            m_head = head;
            m_tail = tail;
        }

        public int getHead() {
            return m_head;
        }

        public int getTail() {
            return m_tail;
        }
    }

    private Animator() {

        m_edges = new ArrayList<Edge>();
        m_selectedEdges = new ArrayList<Edge>();
        m_delay = 1500;
        m_iterationType = IterationType.TIMER;
    }

    public void init() {

        ObservableGraph<String, Integer> og = new ObservableGraph<String, Integer>(GraphBuilder.getInstance().getGraph());
        og.addGraphEventListener(new GraphEventListener<String, Integer>() {
            @Override
            public void handleGraphEvent(GraphEvent<String, Integer> evt) {
                System.err.println(evt);
            }
        });

        m_graph = og;
        m_layout = new FRLayout<String, Integer>(m_graph);
        m_layout.setSize(new Dimension(600, 600));

        Relaxer relaxer = new VisRunner((IterativeContext) m_layout);
        relaxer.stop();
        relaxer.prerelax();

        Layout<String, Integer> staticLayout =
                new StaticLayout<String, Integer>(m_graph, m_layout);

        m_viewer = new VisualizationViewer<String, Integer>(m_layout, new Dimension(600, 600));

        JRootPane rp = InterfaceBuilder.getMainFrame().getRootPane();
        rp.putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE);

        InterfaceBuilder.getMainFrame().getContentPane().setLayout(new BorderLayout());
        InterfaceBuilder.getMainFrame().getContentPane().setBackground(java.awt.Color.lightGray);
        InterfaceBuilder.getMainFrame().getContentPane().setFont(new Font("Serif", Font.PLAIN, 12));

        DefaultModalGraphMouse gm = new DefaultModalGraphMouse();
        gm.setMode(ModalGraphMouse.Mode.PICKING);
        m_viewer.setGraphMouse(gm);

        m_viewer.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.CNTR);
        m_viewer.getRenderContext().setVertexLabelTransformer(new ToStringLabeller<String>());
        m_viewer.setForeground(Color.white);

        m_viewer.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentResized(ComponentEvent arg0) {
                super.componentResized(arg0);
                m_layout.setSize(arg0.getComponent().getSize());
            }
        });

        InterfaceBuilder.getMainFrame().getContentPane().removeAll();
        InterfaceBuilder.getMainFrame().getContentPane().add(m_viewer);
    }

    public void start(AnimationType type) {
        TimerTask task;
        if (AnimationType.ADDING_VERTICES == type) {
            clear();
            task = new Animator.AddVertexTask();
        } else {
            m_verticesLeft = m_graph.getEdgeCount();
            task = new Animator.PrimVisualizationTask();
        }

        m_timer = new Timer();
        m_timer.schedule(task, m_delay, m_delay);
        m_viewer.repaint();
    }

    public void clear() {
        init();
        GraphBuilder.getInstance().clearGraph();
    }

    private void addVertex(int verticesCount) {

        m_viewer.getRenderContext().getPickedVertexState().clear();
        m_viewer.getRenderContext().getPickedEdgeState().clear();

        try {
            if (m_graph.getVertexCount() < verticesCount) {
                int toAdd = m_graph.getVertexCount();

                m_graph.addVertex("V" + toAdd);
                m_viewer.getRenderContext().getPickedVertexState().pick("V" + toAdd, true);

                // wire it to some edges
                if (m_previousVertex >= 0) {
                    Integer edge = m_graph.getEdgeCount();
                    m_viewer.getRenderContext().getPickedEdgeState().pick(edge, true);
                    //m_graph.addEdge(edge, "V" + m_previousVertex, "V" + toAdd);

                    //connecting to random vertex
                    int rand = (int) (Math.random() * m_graph.getVertexCount());
                    while (rand == toAdd 
                            || m_edges.contains(new Edge(rand, toAdd))
                            || m_edges.contains(new Edge(rand, rand))) {
                        rand = (int) (Math.random() * m_graph.getVertexCount());
                    }
                    edge = m_graph.getEdgeCount();
                    m_viewer.getRenderContext().getPickedEdgeState().pick(edge, true);

                    m_graph.addEdge(edge, "V" + toAdd, "V" + rand);
                    m_edges.add(new Edge(toAdd, rand));
                }

                m_previousVertex = toAdd;

                m_layout.initialize();

                Relaxer relaxer = new VisRunner((IterativeContext) m_layout);
                relaxer.stop();
                relaxer.prerelax();
                StaticLayout<String, Integer> staticLayout =
                        new StaticLayout<String, Integer>(m_graph, m_layout);
                LayoutTransition<String, Integer> lt =
                        new LayoutTransition<String, Integer>(m_viewer, m_viewer.getGraphLayout(),
                        staticLayout);
                edu.uci.ics.jung.visualization.util.Animator animator = new edu.uci.ics.jung.visualization.util.Animator(lt);
                animator.start();

                m_viewer.repaint();

            } else {
                m_done = true;
            }

        } catch (Exception e) {
            System.out.println(e);
        }
    }

    private void getNextMSTEdge() {

        m_viewer.getRenderContext().getPickedVertexState().clear();
        m_viewer.getRenderContext().getPickedEdgeState().clear();

        try {
            m_verticesLeft = m_graph.getVertexCount();
            if (m_verticesLeft > 0) {
                Random r = new Random();
                int rand = r.nextInt(m_graph.getEdgeCount());

                m_graph.removeEdge(m_graph.getEdgeCount() - 1);
                int currentEdge = m_graph.getEdgeCount() - 1;
                //int edge = m_graph.
                m_viewer.getRenderContext().getPickedEdgeState().pick(currentEdge, true);
                m_viewer.getRenderContext().getPickedVertexState().pick(m_graph.getSource(currentEdge), true);
                m_viewer.getRenderContext().getPickedVertexState().pick(m_graph.getDest(currentEdge), true);
                m_viewer.repaint();
            } else {
                m_done = true;
            }

        } catch (Exception e) {
            System.out.println(e);
        }
    }

    private void waitForKey() throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(1);
        KeyEventDispatcher dispatcher = new KeyEventDispatcher() {
            // Anonymous class invoked from EDT
            @Override
            public boolean dispatchKeyEvent(KeyEvent e) {
                if (KeyEvent.VK_SPACE == e.getKeyCode()) {
                    latch.countDown();
                }
                return false;
            }
        };
        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(dispatcher);
        latch.await();  // current thread waits here until countDown() is called
        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(dispatcher);
    }

    public void setVertexCount(int value) {
        m_verticesLeft = value;
    }

    public int getDelay() {
        return m_delay;
    }

    public void setDelay(int delay) {
        m_delay = delay;
    }

    public void setVerticesCount(int verticesLeft) {
        m_verticesLeft = verticesLeft;
    }

    public enum AnimationType {

        ADDING_VERTICES, PRIMS_ALGORITHM
    };

    public enum IterationType {

        TIMER, ARROWS
    };

    private class AddVertexTask extends TimerTask {

        @Override
        public void run() {
            if (IterationType.ARROWS == m_iterationType) {
                try {
                    waitForKey();
                } catch (InterruptedException ex) {
                    Logger.getLogger(Animator.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            addVertex(m_verticesLeft);

            if (m_done) {
                cancel();
                m_done = false;
            }
        }
    }

    private class PrimVisualizationTask extends TimerTask {

        @Override
        public void run() {
            if (IterationType.ARROWS == m_iterationType) {
                try {
                    waitForKey();
                } catch (InterruptedException ex) {
                    Logger.getLogger(Animator.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            getNextMSTEdge();
            if (m_done) {
                cancel();
                m_done = false;
            }
        }
    }
}
